4. Content Caching

The @Cacheable and @CacheFlush annotations can be applied to controller actions and the plugin will then cache the page fragment generated by the controller whether this is done by rendering a GSP, using a MarkupBuilder closure, rendering text directly or whatever. Only successful page renders are cached, so redirects, 404s, errors and so on will not be.

Composing pages so that they can be optimally cached requires some thought. The plugin uses a servlet filter that runs 'inside' the SiteMesh filter provided by Grails. This means that cached output is decorated by SiteMesh and the resulting page can therefore contain uncached content from the SiteMesh template. In addition you can use caching at a modular level to cache the output of controller actions invoked using the g:include tag, or by caching taglib tags. Combining these techniques leads to powerful modular page caching. For example, you can cache the output of the 'main' controller then use g:include tags, or taglib tags, in the SiteMesh layout to include content on the page that is cached separately - and can be flushed separately - from the main body of the page.

4.1. Caching and Flushing with Controller Actions

Example: caching Grails CRUD pages

Grails' standard scaffolded CRUD pages provide a good example of how caching and flushing can be applied. For example, let's take an Album domain class. The scaffolded controller could be annotated like this:

AlbumController.groovy

class AlbumController {
    // the index action is uncached as it just performs a redirect to list
    def index = {
        redirect(action: "list", params: params)
    }

@Cacheable("albumControllerCache") def list = { // standard Grails scaffolding code omitted }

@Cacheable("albumControllerCache") def create = { // standard Grails scaffolding code omitted }

@CacheFlush(["albumControllerCache", "artistControllerCache", "latestControllerCache", "popularControllerCache"]) def save = { // standard Grails scaffolding code omitted }

@Cacheable("albumControllerCache") def show = { // standard Grails scaffolding code omitted }

@Cacheable("albumControllerCache") def edit = { // standard Grails scaffolding code omitted }

@CacheFlush(["albumControllerCache", "latestControllerCache", "popularControllerCache"]) def update = { // standard Grails scaffolding code omitted }

@CacheFlush(["albumControllerCache", "artistControllerCache", "latestControllerCache", "popularControllerCache"]) def delete = { // standard Grails scaffolding code omitted } }

The list, show, create and edit pages are all cached. The show and edit rely on an domain object id parameter and this will be included in the cache key so that /album/show/1 and /album/show/2 are cached separately. The save, update and delete actions will flush caches. Note that in addition to flushing the cache used by the list, show, create and edit actions they are flushing other caches which are content caches for controllers whose output should be refreshed if Album data changes.

4.2. Content Caching and SiteMesh

Example: decorating a cached page with dynamic content using SiteMesh

It is often necessary to have portions of a page be dynamic. A typical example is when something is displayed to logged in users that will be different for each user. Those sorts of page sections are not really candidates for caching. At the same time other parts of the page may well be able to take advantage of caching. For example, if you want to display a "Welcome back $username" type message in page headers while caching the main body of the page you can use SiteMesh templates like this:

grails-app/views/layouts/main.gsp

<html>
    <head>
        <title><g:layoutTitle default="Welcome to My Grails Application"/></title>
        <%-- render the page head from the controller - may be cached --%>
        <g:layoutHead/>
    </head>
    <body>
        <%-- render a "welcome back" header (tags used here are from the Spring Security plugin) --%>
        <g:isLoggedIn>
            <div id="loggedInUser"><g:message code="auth.loggedInAs" args="[loggedInUsername()]" default="Logged in as {0}"/></div>
        </g:isLoggedIn>
        <g:isNotLoggedIn>
            <div id="loginLink"><g:link controller="login"><g:message code="default.login.label" default="Login here"/></g:link></div>
        </g:isNotLoggedIn>

<%-- render the page body from the controller - may be cached --%> <g:layoutBody/> </body> </html>

If the controller action invoked uses @Cacheable everything will work fine because the content of the SiteMesh layout is not cached - only the content generated by the cached action. The SiteMesh template is applied to cached and uncached content alike so the correct username will be displayed to your users even though the main body of the page may have been loaded from a cache.

Example: a modular page using multiple cached sections

One of the most powerful features of page fragment caching is that the generated page can be composed from multiple cached sections. This is accomplished using Grails' g:include tag. For example, in this page the main body of the page is rendered by some controller action and the output of other controllers are included in the SiteMesh layout using the g:include tag:

grails-app/views/layouts/main.gsp

<html>
    <head>
        <title><g:layoutTitle default="Welcome to My Grails Application"/></title>
        <%-- render the page head from the controller - may be cached --%>
        <g:layoutHead/>
    </head>
    <body>
        <%-- render the page body from the controller - may be cached --%>
        <g:layoutBody/>

<div class="sidebar"> <%-- each of these controller actions can be cached separately as well --%> <g:include controller="latest" action="albums"/> <g:include controller="popular" action="albums"/> </div> </body> </html>

LatestController.groovy

@Cacheable("latestAlbums")
def albums = {
    def albums = Album.list(sort: "dateCreated", order: "desc", max: 10)
    [albumInstanceList: albums]
}

LatestController.groovy

@Cacheable("popularAlbums")
def albums = {
    def albums = Album.listOrderByAverageRating(max: 10)
    return [albumInstanceList: albums]
}

If all the caches are hit the final rendered page will be composed of 3 separate cached sections. What is more, each individual section can be flushed without affecting the others so with some thought about how to compose your page and apply your caches you can optimise cache usage without delivering stale data to the user.

4.3. Using Annotations at Class Level

The @Cacheable and @CacheFlush annotations can be applied to controllers at class level. This is more likely useful with @Cacheable but it is certainly possible to apply @CacheFlush at class level so that any action on that controller will flush a set of caches. Any annotation on an individual action will be applied in preference to an annotation at class level, so a class level annotation behaves like a default. An annotation at class level will work with dynamic scaffolded actions so you don't have to generate a concrete action in order to benefit from caching behaviour.

@Cacheable("albumControllerCache")
class AlbumController {

static scaffold = true // all dynamically scaffolded actions will be cached

@Cacheable("albumListCache") def list = { // … }

@CacheFlush(/albumw+Cache/) def save = { // … }

def show = { // … } }

In this example:

4.4. Cache Headers

Content caching in the Springcache plugin attempts to respect any cache-control headers present in the original response. Specifically the plugin will handle certain response headers as follows:

Cache-Control: no-cache

If this header is present in the response the content will not be cached even if there is a @Cacheable annotation present on the controller or action. This allows you to prevent caching in certain circumstances or override the controller-wide caching policy in a particular action.

Cache-Control: max-age=x

If the response is cached the time-to-live of the cache entry is set so that it corresponds to the max-age value in the Cache-Control header. If no such header is present the cache's configured time-to-live is used (see Cache Configuration).

ETag

If the original response set an ETag header Springcache will set the same header if it serves the response from the cache. Additionally, if an incoming request has an If-None-Match header that matches the ETag of the cached response Springcache will send a 304 Not Modified status code and an empty response body instead of the cached response.

Last-Modified

If the original response set a Last-Modified header Springcache will set the same header if it serves the response from the cache. Additionally, if an incoming request has an If-Modified-Since header with a timestamp later than the Last-Modified header of the cached response Springcache will send a 304 Not Modified status code and an empty response body instead of the cached response.

The Cache Headers Plugin

The Springcache plugin integrates well with the Cache Headers plugin. Some examples:

Preventing caching

If you want to prevent Springcache from caching a response in certain circumstances:

@Cacheable("myCache")
def myAction = {
	// …
	if (someConditionHoldsThatMeansThisShouldNotGetCached) {
		cache false
	}
	// …
}

Alternatively you might want to declare @Cacheable at the class level and then exclude a particular action from the cache:

@Cacheable("myCache")
class MyController {

// ...

def myAction = { cache false // … }

Controlling cache expiry

As explained above cache time-to-live will respect the max-age in a cache control header.

@Cacheable("myCache")
def myAction = {
	cache validFor: 3600
	// …
}

In this example the response will be cached with a time-to-live of one hour regardless of the default time-to-live configured on the cache itself.

Sending Not-Modified responses

@Cacheable("myCache")
def show = {
	withCacheHeaders {
		def book = Book.get(params.id)
		etag {
			"${book.ident()}:${book.version}"
		}
		lastModified {
			book.dateCreated ?: book.dateUpdated
		}
		generate {
			render view: "show", model: [item: book]
		}
	}
}

In this example the response will be cached and any subsequent requests that send matching If-Modified-Since and/or If-None-Match headers will be sent a 304 Not Modified response if they hit the cache.

4.5. Content Cache Keys

The Springcache plugin uses an instance of the interface grails.plugin.springcache.key.KeyGenerator to generate the cache key. The default implementation is a bean named springcacheDefaultKeyGenerator which is of type grails.plugin.springcache.web.key.DefaultKeyGenerator. If you want to use a different key generator for a particular action you just need to add the keyGenerator element to the @Cacheable annotation specifying the name of a Spring bean that implements the KeyGenerator interface.

@Cacheable(cache = "albumControllerCache", keyGenerator = "myKeyGenerator")
def list = {
    // …
}

Alternatively you can override the default key generator by redefining the springcacheDefaultKeyGenerator bean in resources.groovy .

The keyGenerator element is only for content caching and just works on controllers, it is ignored by the @Cacheable annotation on service methods and taglibs.

grails.plugin.springcache.web.key.DefaultKeyGenerator

The DefaultKeyGenerator generates a key based on the controller name, action name and any request parameters (which can be from a query string, POST body or those added by Grails URL mappings, e.g. the id parameter on a standard show or edit action).

grails.plugin.springcache.web.key.WebContentKeyGenerator

WebContentKeyGenerator is a multi-purpose KeyGenerator implementation that exposes a number of boolean properties that control key generation. All the properties default to false .

ajax

If true then keys will differ depending on the presence or absence of the X-Requested-With request header so AJAX requests will be cached separately from regular requests. This is useful when you have an action that renders different content when it is requested via AJAX .

contentType

If true keys will differ depending on the requested content format as determined by the format meta-property on HttpServletRequest. This is useful when you use content negotiation in a request so that responses with different formats are cached separately.

See Content Negotiation for more detail.

requestMethod

If true keys will differ depending on the request HTTP method. This is useful for some RESTful controllers (although if different request methods are mapped to different actions you do not need to use this mechanism). GET and HEAD requests are considered the same for the purposes of key generation.

Example configuration

ajaxAwareKeyGenerator(WebContentKeyGenerator) {
	ajax = true
}

contentTypeAwareKeyGenerator(WebContentKeyGenerator) { contentType = true }

4.6. Content Negotiation

By default the key generator used by the page fragment caching filter does not take content negotiation into account. However, if you are caching controller actions that use Grails' withFormat dynamic method to render different content types you will want to cache results separately according to the output format. You can use the WebContentKeyGenerator class to do this. You just need to register a key generator bean with Spring and then annotate any content negotiated actions like this:

grails-app/conf/spring/resources.groovy

mimeTypeAwareKeyGenerator(WebContentKeyGenerator) {
	contentType = true
}

grails-app/controllers/MyController.groovy

@Cacheable(cache = "albumControllerCache", keyGenerator = "mimeTypeAwareKeyGenerator")
def list = {
    def albumList = Album.list()
	withFormat {
		html { [albumList: albumList] }
		xml { render albumList as XML }
		json { render albumList as JSON }
	}
}

4.7. Full Page Caching

The plugin only provides page fragment caching rather than full page caching. Full page caching is very simple to apply using the EhCache-Web library that the Springcache plugin uses. See my blog post here for details.

4.8. TagLib Caching

An alternative to using includes for caching smaller page fragments is to use caching on taglib tags. When a cacheable tag is called, the parameters it is called with from the cache key. If there is no entry in the cache, the tag is executed and the output that it generated is cached (as well as being written to the page). If there is an entry in the cache, the tag is not executed and the cached output is written to the page.

class BlogArticlesTagLib {

static namespace = "blogarticles"

def blogArticlesService

@Cacheable("blogArticlesTagCache") def allArticles = { attrs -> out << "<ul>" blogArticlesService.getArticles(attrs.id).each { out << "<li>${it.title}</li>" } out << "</ul>" } }

When we call the tag like so…

<blogarticles:allArticles id="${blogId}" />

The cache key is formed by the 'id' tag. This tag can be reused across different views without changing the caching semantics. That is, the controller/action that the cacheable tag is called from does not affect the cache key.

Tags with a body

When caching a tag with a body, if there is a cache hit the body will not be executed. Therefore it doesn't make sense to cache a tag that is invoked with a different body unless you are ensuring the right cacheability through the tag parameters (i.e. cache keys).